package com.excalibur.core.security;

import java.io.ByteArrayOutputStream;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;

import com.excalibur.core.util.HexUtil;

/**
 * RSA加密 解密
 * 
 * @author tianbenzhen@pwrd.com,"Jiance Qin"
 * @version 2014-3-18 下午5:56:19
 */
public class RSACoder {

	public static final int NOT_ENCODE = -1;
	public static final int HEX = 0;
	public static final int BASE64 = 1;

	private static final int ENCRIPT_BLOCK_SIZE = 117;
	private static final int DECRIPT_BLOCK_SIZE = 128;

	/**
	 * 私钥解密
	 * 
	 * @param data
	 *            加密后Base64 Encode String串
	 * @param privateKey
	 * @param encodeMode
	 *            编码类型，hex或base64
	 * @param keyEncodeMode
	 *            解密后原始String key编码类型，hex或base64
	 * @return String
	 * @throws Exception
	 */
	public static String decryptByPrivateKey(String data, String privateKey, int encodeMode, int keyEncodeMode)
			throws Exception {
		byte[] encData = null;
		if (encodeMode == HEX) {
			encData = HexUtil.toByteArray(data);
		} else if (encodeMode == BASE64) {
			encData = Base64.decodeBase64(data);
		} else {
			encData = data.getBytes("UTF-8");
		}
		RSAPrivateKey rsaPrivateKey = RSAKeyUtils.loadRSAPrivateKey(privateKey, keyEncodeMode);
		Cipher cipher = Cipher.getInstance(rsaPrivateKey.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
		return new String(doFinalBlock(cipher, encData, DECRIPT_BLOCK_SIZE), "UTF-8");
	}

	/**
	 * 私钥解密
	 * 
	 * @param data
	 *            加密后Base64 Encode String串
	 * @param privateKey
	 * @param encodeMode
	 *            编码类型，hex或base64
	 * @param keyEncodeMode
	 *            解密后原始String key编码类型，hex或base64
	 * @return String
	 * @throws Exception
	 */
	public static byte[] decryptByPrivateKeyReturnByte(String data, String privateKey, int encodeMode, int keyEncodeMode)
			throws Exception {
		byte[] encData = null;
		if (encodeMode == HEX) {
			encData = HexUtil.toByteArray(data);
		} else if (encodeMode == BASE64) {
			encData = Base64.decodeBase64(data);
		} else {
			encData = data.getBytes("UTF-8");
		}
		RSAPrivateKey rsaPrivateKey = RSAKeyUtils.loadRSAPrivateKey(privateKey, keyEncodeMode);
		Cipher cipher = Cipher.getInstance(rsaPrivateKey.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
		return doFinalBlock(cipher, encData, DECRIPT_BLOCK_SIZE);
	}

	/**
	 * 公钥加密
	 * 
	 * @param data
	 *            原始数据
	 * @param publicKey
	 * @param encodeMode
	 *            编码类型，hex或base64，默认为base64
	 * @param keyEncodeMode
	 *            解密后原始String key编码类型，hex或base64
	 * @return String Base64 Encode String
	 * @throws Exception
	 */
	public static String encryptByPublicKey(String data, String publicKey, int encodeMode, int keyEncodeMode)
			throws Exception {
		RSAPublicKey rsaPublicKey = RSAKeyUtils.loadRSAPublicKey(publicKey, keyEncodeMode);
		Cipher cipher = Cipher.getInstance(rsaPublicKey.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
		byte[] encData = doFinalBlock(cipher, data.getBytes("UTF-8"), ENCRIPT_BLOCK_SIZE);
		if (encodeMode == HEX) {
			return HexUtil.toHexString(encData);
		} else if (encodeMode == BASE64) {
			return Base64.encodeBase64String(encData);
		} else {
			return new String(encData, "UTF-8");
		}
	}

	/**
	 * 公钥解密
	 * 
	 * @param data
	 *            加密后Base64 Encode String串
	 * @param publicKey
	 * @param encodeMode
	 *            编码类型，hex或base64，默认为base64
	 * @param keyEncodeMode
	 *            解密后原始String key编码类型，hex或base64
	 * @return String 解密后原始String
	 * @throws Exception
	 */
	public static String decryptByPublicKey(String data, String publicKey, int encodeMode, int keyEncodeMode)
			throws Exception {
		byte[] encData = null;
		if (encodeMode == HEX) {
			encData = HexUtil.toByteArray(data);
		} else if (encodeMode == BASE64) {
			encData = Base64.decodeBase64(data);
		} else {
			encData = data.getBytes("UTF-8");
		}
		RSAPublicKey rsaPublicKey = RSAKeyUtils.loadRSAPublicKey(publicKey, keyEncodeMode);
		Cipher cipher = Cipher.getInstance(rsaPublicKey.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
		return new String(doFinalBlock(cipher, encData, DECRIPT_BLOCK_SIZE), "UTF-8");
	}

	/**
	 * 公钥解密
	 * 
	 * @param data
	 *            加密后Base64 Encode String串
	 * @param publicKey
	 * @param encodeMode
	 *            编码类型，hex或base64，默认为base64
	 * @param keyEncodeMode
	 *            解密后原始String key编码类型，hex或base64
	 * @return String 解密后原始String
	 * @throws Exception
	 */
	public static byte[] decryptByPublicKeyReturnByte(String data, String publicKey, int encodeMode, int keyEncodeMode)
			throws Exception {
		byte[] encData = null;
		if (encodeMode == HEX) {
			encData = HexUtil.toByteArray(data);
		} else if (encodeMode == BASE64) {
			encData = Base64.decodeBase64(data);
		} else {
			encData = data.getBytes("UTF-8");
		}
		RSAPublicKey rsaPublicKey = RSAKeyUtils.loadRSAPublicKey(publicKey, keyEncodeMode);
		Cipher cipher = Cipher.getInstance(rsaPublicKey.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
		return doFinalBlock(cipher, encData, DECRIPT_BLOCK_SIZE);
	}

	/**
	 * 私钥加密
	 * 
	 * @param data
	 *            原始数据
	 * @param privateKey
	 * @param encodeMode
	 *            编码类型，hex或base64，默认为base64
	 * @param keyEncodeMode
	 *            解密后原始String key编码类型，hex或base64
	 * @return String Base64 Encode String
	 * @throws Exception
	 */
	public static String encryptByPrivateKey(String data, String privateKey, int encodeMode, int keyEncodeMode)
			throws Exception {
		RSAPrivateKey rsaPrivateKey = RSAKeyUtils.loadRSAPrivateKey(privateKey, keyEncodeMode);
		Cipher cipher = Cipher.getInstance(rsaPrivateKey.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, rsaPrivateKey);
		byte[] encData = doFinalBlock(cipher, data.getBytes("UTF-8"), ENCRIPT_BLOCK_SIZE);
		if (encodeMode == HEX) {
			return HexUtil.toHexString(encData);
		} else if (encodeMode == BASE64) {
			return Base64.encodeBase64String(encData);
		} else {
			return new String(encData, "UTF-8");
		}
	}

	private static byte[] doFinalBlock(final Cipher cipher, final byte[] data, final int step) throws Exception {
		if (data.length <= step) {
			return cipher.doFinal(data);
		} else {
			final ByteArrayOutputStream bos = new ByteArrayOutputStream();
			for (int i = 0; i < data.length; i += step) {
				final int len = (i + step) < data.length ? step : data.length - i;
				final byte[] b = cipher.doFinal(data, i, len);
				bos.write(b);
			}
			return bos.toByteArray();
		}
	}
}
